package com.zff.rental.utils.specification;

import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;

import java.util.*;

/**
 * Specification生成工具
 *
 * @author HCY
 */
public final class SpecBuilder<T> {
    private final List<Specification<T>> preList = new ArrayList<>();

    private SpecBuilder() {
    }

    public static <ROOT> SpecBuilder<ROOT> builder() {
        return new SpecBuilder<>();
    }

    public SpecBuilder<T> isNull(String name) {
        return isNull(false, name);
    }

    public SpecBuilder<T> isNull(boolean isIgnore, String name) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NullPre<>(name));
    }

    public SpecBuilder<T> isNotNull(String name) {
        return isNotNull(false, name);
    }

    public SpecBuilder<T> isNotNull(boolean isIgnore, String name) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotNullPre<>(name));
    }

    public SpecBuilder<T> equal(String name, Object value) {
        if (SpecConfig.getInstance().isEqualAllowNull() && value == null) {
            return isNull(name);
        }
        return equal(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> equal(boolean isIgnore, String name, Object value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new EqualPre<>(name, value));
    }

    public SpecBuilder<T> notEqual(String name, Object value) {
        if (SpecConfig.getInstance().isNotEqualAllowNull()) {
            return isNotNull(name);
        }
        return notEqual(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> notEqual(boolean isIgnore, String name, Object value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotEqualPre<>(name, value));
    }

    public SpecBuilder<T> like(String name, String value) {
        if (SpecConfig.getInstance().isLikeAllowEmpty()) {
            return isNull(name);
        }
        return like(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> like(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LikePre<>(name, value));
    }

    public SpecBuilder<T> likeAll(String name, String value) {
        if (SpecConfig.getInstance().isLikeAllowEmpty()) {
            return isNull(name);
        }
        return likeAll(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> likeAll(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LikeAllPre<>(name, value));
    }

    public SpecBuilder<T> likeRight(String name, String value) {
        if (SpecConfig.getInstance().isLikeAllowEmpty()) {
            return isNull(name);
        }
        return likeRight(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> likeRight(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LikeRightPre<>(name, value));
    }

    public SpecBuilder<T> likeLeft(String name, String value) {
        if (SpecConfig.getInstance().isLikeAllowEmpty()) {
            return isNull(name);
        }
        return likeLeft(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> likeLeft(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LikeLeftPre<>(name, value));
    }

    public SpecBuilder<T> notLike(String name, String value) {
        if (SpecConfig.getInstance().isNotLikeAllowEmpty()) {
            return isNotNull(name);
        }
        return like(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> notLike(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotLikePre<>(name, value));
    }

    public SpecBuilder<T> notLikeAll(String name, String value) {
        if (SpecConfig.getInstance().isNotLikeAllowEmpty()) {
            return isNotNull(name);
        }
        return likeAll(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> notLikeAll(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotLikeAllPre<>(name, value));
    }

    public SpecBuilder<T> notLikeRight(String name, String value) {
        if (SpecConfig.getInstance().isNotLikeAllowEmpty()) {
            return isNotNull(name);
        }
        return likeRight(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> notLikeRight(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotLikeRightPre<>(name, value));
    }

    public SpecBuilder<T> notLikeLeft(String name, String value) {
        if (SpecConfig.getInstance().isNotLikeAllowEmpty()) {
            return isNotNull(name);
        }
        return likeLeft(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> notLikeLeft(boolean isIgnore, String name, String value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new NotLikeLeftPre<>(name, value));
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> greaterThan(String name, Y value) {
        return greaterThan(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> greaterThan(boolean isIgnore, String name, Y value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new GreaterThanPre<>(name, value));
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> greaterThanOrEqualTo(String name, Y value) {
        return greaterThanOrEqualTo(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> greaterThanOrEqualTo(boolean isIgnore, String name, Y value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new GreaterThanOrEqualPre<>(name, value));
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> lessThan(String name, Y value) {
        return lessThan(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> lessThan(boolean isIgnore, String name, Y value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LessThanPre<>(name, value));
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> lessThanOrEqualTo(String name, Y value) {
        return lessThanOrEqualTo(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> lessThanOrEqualTo(boolean isIgnore, String name, Y value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LessThanOrEqualPre<>(name, value));
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> between(String name, Y value, Y value2) {
        if (value == null && value2 != null) {
            return lessThanOrEqualTo(name, value2);
        }
        if (value != null && value2 == null) {
            return greaterThanOrEqualTo(name, value);
        }
        return between(SpecConfig.isIgnoreByValue(value) || SpecConfig.isIgnoreByValue(value2), name, value, value2);
    }

    public <Y extends Comparable<? super Y>> SpecBuilder<T> between(boolean isIgnore, String name, Y value, Y value2) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new BetweenPre<>(name, value, value2));
    }

    public SpecBuilder<T> in(String name, Object[] value) {
        return in(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> in(boolean isIgnore, String name, Object[] value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new InArrayPre<>(name, value));
    }

    public SpecBuilder<T> in(String name, Collection<?> value) {
        return in(SpecConfig.isIgnoreByValue(value), name, value);
    }

    public SpecBuilder<T> in(boolean isIgnore, String name, Collection<?> value) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new InCollectionPre<>(name, value));
    }

    /**
     * 包含任意元素
     */
    public SpecBuilder<T> likeAllIn(String name, Collection<String> inStr) {
        return likeAllIn(SpecConfig.isIgnoreByValue(inStr), name, inStr);
    }

    public SpecBuilder<T> likeAllIn(boolean isIgnore, String name, Collection<String> inStr) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name), new LikeAllIn<>(name, inStr));
    }

    /**
     * 包含所有元素
     */
    public SpecBuilder<T> likeAllAnd(String name, Collection<String> inStr) {
        return likeAllIn(SpecConfig.isIgnoreByValue(inStr), name, inStr);
    }

    public SpecBuilder<T> likeAllAnd(boolean isIgnore, String name, Collection<String> inStr) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name),
                new LikeAllAnd<>(name, inStr));
    }

    /**
     * 不包含集合任意一个元素
     */
    public SpecBuilder<T> likeAllNotIn(String name, Collection<String> inStr) {
        return likeAllIn(SpecConfig.isIgnoreByValue(inStr), name, inStr);
    }

    public SpecBuilder<T> likeAllNotIn(boolean isIgnore, String name, Collection<String> inStr) {
        return add(isIgnore || SpecConfig.isIgnoreByName(name),
                new LikeAllNotIn<>(name, inStr));
    }

    public SpecBuilder<T> add(Specification<T> pre) {
        return add(false, pre);
    }

    public SpecBuilder<T> add(boolean isIgnore, Specification<T> pre) {
        if (!isIgnore && pre != null) {
            preList.add(pre);
        }
        return this;
    }

    /**
     * 多where条件and连接
     */
    public Specification<T> buildAnd() {
        return build(true);
    }

    /**
     * 多where条件or连接
     */
    public Specification<T> buildOr() {
        return build(false);
    }

    private Specification<T> build(boolean isAnd) {
        return (root, query, builder) -> {
            final HashMap<String, Join<Object, Object>> joinCache = new HashMap<>();
            Predicate[] predicates = preList.stream()
                    .filter(Objects::nonNull)
                    .map(pre -> {
                        if (pre instanceof BasePre) {
                            return ((BasePre<T, ?, ?>) pre).toPredicateByCacheJoin(root, query, builder, joinCache);
                        } else {
                            return pre.toPredicate(root, query, builder);
                        }
                    })
                    .toArray(Predicate[]::new);

            if (predicates.length == 0) {
                return null;
            } else if (predicates.length == 1) {
                return predicates[0];
            }
            if (isAnd) {
                return builder.and(predicates);
            } else {
                return builder.or(predicates);
            }
        };
    }
}
